
#include "system.h"
#include "time.h"
#include "utils.h"

#include <stdio.h>
#include <sys/time.h>

static bool PUP_TIMESTAMPS_INITIALIZED = false;
static bool PUP_TIMESTAMPS_USE_TSC = false;
static uint64_t PUP_TSC_FREQUENCY = 0;
static pup_timestamp_t (*PUP_TIMESTAMPER)();

static inline uint64_t pup_read_tsc() __attribute__((optimize("O0")));
static void pup_timestamps_init();
static void pup_timestamps_determine_tsc_frequency();
static pup_timestamp_t pup_timestamp_now_tsc();
static pup_timestamp_t pup_timestamp_now_non_tsc();

pup_timestamp_t pup_timestamp_now()
{
  if (!PUP_TIMESTAMPS_INITIALIZED)
    pup_timestamps_init();
  return PUP_TIMESTAMPER();
}

pup_timespan_t pup_timespan_diff(pup_timestamp_t before, pup_timestamp_t after)
{
  pup_timespan_t timespan;
  uint64_t const value_diff = after - before;
  if (PUP_TIMESTAMPS_USE_TSC)
    timespan.nanoseconds = 1000000000 * value_diff / PUP_TSC_FREQUENCY;
  else
    timespan.nanoseconds = value_diff;
  return timespan;
}

pup_timespan_t pup_timespan_since(pup_timestamp_t timestamp)
{
  pup_timestamp_t const now = pup_timestamp_now();
  return pup_timespan_diff(timestamp, now);
}

static inline uint64_t pup_read_tsc()
{
  unsigned high, low;
  __asm volatile(
    "rdtscp" : "=a"(low), "=d"(high));
  return (PUP_STATIC_CAST(uint64_t,high) << 32) | PUP_STATIC_CAST(uint64_t,low);
}

static void pup_timestamps_init()
{
  PUP_TIMESTAMPS_USE_TSC = false;

  // Check for Invariant TSC
  unsigned eax, ebx, ecx, edx;
  pup_cpuid(0x80000007, &eax, &ebx, &ecx, &edx);
  bool use_tsc = ((edx & 0x100) != 0);

  if (use_tsc) {
    pup_timestamps_determine_tsc_frequency();
    PUP_TIMESTAMPER = &pup_timestamp_now_tsc;
    PUP_TIMESTAMPS_USE_TSC = true;
  } else 
    PUP_TIMESTAMPER = &pup_timestamp_now_non_tsc;
  PUP_TIMESTAMPS_INITIALIZED = true;
}

static void pup_timestamps_determine_tsc_frequency()
{
  // warm up
  for(unsigned i = 0; i != 1000; ++i)
    pup_read_tsc();

  // measure time of and ticks between rdtsc calls
  register uint64_t end;
  uint64_t begin = pup_read_tsc();
  pup_timestamp_t before = pup_timestamp_now_non_tsc();
  for(unsigned i = 0; i != 10000000; ++i)
    end = pup_read_tsc();
  pup_timestamp_t after = pup_timestamp_now_non_tsc();
  pup_timespan_t const span = pup_timespan_diff(before, after);
  double const nanos_per_read_tsc = PUP_STATIC_CAST(double,pup_timespan_nanos(&span)) / 10000000;
  double const ticks_between_read_tsc = PUP_STATIC_CAST(double,end-begin) / 10000000;
  double const ticks_per_nano = ticks_between_read_tsc / nanos_per_read_tsc;
  //printf("nanos per read_tsc: %f\n", nanos_per_read_tsc);
  //printf("ticks between read_tsc: %f\n", ticks_between_read_tsc);
  PUP_TSC_FREQUENCY = PUP_STATIC_CAST(uint64_t, (1000000000.0 * ticks_per_nano));
  //printf("TSC_FREQUENCY:%llu\n", PUP_STATIC_CAST(unsigned long long, PUP_TSC_FREQUENCY));
}

static inline pup_timestamp_t pup_timestamp_now_tsc()
{
  return pup_read_tsc();
}

static pup_timestamp_t pup_timestamp_now_non_tsc()
{
  static uint64_t NANOS_PER_SECOND = 1000000000;
  static uint64_t NANOS_PER_MICRO  = 1000;
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return (tv.tv_sec * NANOS_PER_SECOND) + (tv.tv_usec * NANOS_PER_MICRO);
}


